#define lib_filesystem_c
#define LUA_LIB

extern "C" {
#include "../lua.h"
#include "../lauxlib.h"
#include "../lualib.h"

#include "../lj_def.h" // Included always for the typedefs
#include "../lj_lib.h" // Included always for the typedefs
#include "../lj_err.h"
}

#undef cast
#define GVL_EXCLUDE_STDINT 1 // lj2 has type typedefs

#include "util.hpp"
#include <gvl/io/encoding.hpp>
#include <gvl/io/fstream.hpp>

#include "filesystem/paths.hpp"
#include "filesystem/7zip.hpp"

/* ------------------------------------------------------------------------ */

LUA_CHECK_FUNC(gvl::octet_stream_reader, reader, UDTYPE_STREAMS_READER)
LUA_CHECK_FUNC(gvl::octet_stream_writer, writer, UDTYPE_STREAMS_WRITER)

extern "C" {

// <Reader methods>

#define LJLIB_MODULE_filesystem_reader_method

LJLIB_CF(filesystem_reader_method_close)
{
	gvl::octet_stream_reader& r = check_reader(L, 1);

	r.detach();
	return 0;
}

LJLIB_CF(filesystem_reader_method___gc)
{
	gvl::octet_stream_reader& r = check_reader(L, 1);

	r.detach();
	return 0;
}

LJ_FUNCA int32_t LJ_FASTCALL lj_filesystem_reader_get(void* reader)
{
	gvl::octet_stream_reader* r = static_cast<gvl::octet_stream_reader*>(reader);

	uint8_t b;
	if(r->try_get(b))
		return -1;
	return b;
}

LJLIB_CF(filesystem_reader_method_get)  LJLIB_REC(.)
{
	gvl::octet_stream_reader& r = check_reader(L, 1);
	
	uint8_t b;
	if(r.try_get(b))
		return 0;

	setnumV(L->top++, (lua_Number)(int32_t)b);
	return 1;
}

LJLIB_PUSH(top-1) LJLIB_SET(__index)

// </Reader methods>

// <Writer methods>

#define LJLIB_MODULE_filesystem_writer_method

LJLIB_CF(filesystem_writer_method_close)
{
	gvl::octet_stream_writer& w = check_writer(L, 1);

	w.flush();
	w.detach();
	return 0;
}

LJLIB_CF(filesystem_writer_method___gc)
{
	gvl::octet_stream_writer& w = check_writer(L, 1);
	
	w.flush();
	w.detach();
	return 0;
}

LJ_FUNCA int32_t LJ_FASTCALL lj_filesystem_writer_put(void* writer, int32_t b)
{
	gvl::octet_stream_writer* w = static_cast<gvl::octet_stream_writer*>(writer);

	if(w->put(b))
		return 1;
	return 0;
}

LJLIB_CF(filesystem_writer_method_put)  LJLIB_REC(.)
{
	gvl::octet_stream_writer& w = check_writer(L, 1);
	int32_t b = lj_lib_checkint(L, 2);

	if(w.put(b))
	{
		setnumV(L->top++, 1.0);
		return 1;
	}

	setnumV(L->top++, 0.0);
	return 1;
}

LJLIB_PUSH(top-1) LJLIB_SET(__index)

// </Writer methods>

#define LJLIB_MODULE_filesystem

// TODO: __gc

LJLIB_PUSH(top-2) LJLIB_SET(!) // Reader metatable

LJLIB_CF(filesystem_open_7zip_r)
{
	char const* path = strdata(lj_lib_checkstr(L, 1));

	{ // Scope to ensure nativePath is destructed before lj_err_argtype
		gvl::string nativePath;
		PathType type = pathToNativePath(path, nativePath);

		if(type == Path_WorkingDir || type == Path_AppData)
		{
			gvl::stream_ptr f(new gvl::fstream(nativePath.c_str(), "rb"));

			SevenZip sz(f);
			bool ret = sz.load();

			return 0;
		}
	}

	lj_err_argtype(L, 1, "valid path"); // TODO: Fix (possible) nativePath memory leak
}

LJLIB_CF(filesystem_open_file_r)
{
	char const* path = strdata(lj_lib_checkstr(L, 1));

	{ // Scope to ensure nativePath is destructed before lj_err_argtype
		gvl::string nativePath;
		PathType type = pathToNativePath(path, nativePath);

		if(type == Path_WorkingDir || type == Path_AppData)
		{
			void* mem = lua_create_object(L, UDTYPE_STREAMS_READER, sizeof(gvl::octet_stream_reader));

			// TODO: This may throw
			gvl::octet_stream_reader* reader = new (mem) gvl::octet_stream_reader(
				gvl::stream_ptr(new gvl::fstream(nativePath.c_str(), "rb")));

			return 1;
		}
	}

	lj_err_argtype(L, 1, "valid path"); // TODO: Fix (possible) nativePath memory leak
}

LJLIB_PUSH(top-3) LJLIB_SET(!) // Writer metatable

LJLIB_CF(filesystem_open_file_w)
{
	char const* path = strdata(lj_lib_checkstr(L, 1));

	{ // Scope to ensure nativePath is destructed before lj_err_argtype
		gvl::string nativePath;
		PathType type = pathToNativePath(path, nativePath);

		if(type == Path_WorkingDir || type == Path_AppData)
		{
			void* mem = lua_create_object(L, UDTYPE_STREAMS_WRITER, sizeof(gvl::octet_stream_writer));

			// TODO: This may throw
			gvl::octet_stream_writer* writer = new (mem) gvl::octet_stream_writer(
				gvl::stream_ptr(new gvl::fstream(nativePath.c_str(), "wb")));
	
			return 1;
		}
	}

	lj_err_argtype(L, 1, "valid path"); // TODO: Fix (possible) nativePath memory leak
}

// TODO: Tracing



#include "../lj_libdef.h"
#include "../lj_lib.h"

LUALIB_API int luaopen_filesystem(lua_State *L)
{
	// TODO: Get the directory of the 
	LJ_LIB_REG(L, filesystem_writer_method);
	LJ_LIB_REG(L, filesystem_reader_method);
	LJ_LIB_REG(L, filesystem);
	return 1;
}

} // extern "C"
